#!/usr/bin/python2.4


import BaseHTTPServer
import cgi
import itertools
import mimetypes
import os
import re
import socket
import sys
import threading
import time
import traceback
import urllib
import urlparse

from logger import logger
from tag import tag
import utils


class RequestHandler(BaseHTTPServer.BaseHTTPRequestHandler):

  handlers = []
  path_cache = {}

  @classmethod
  def register_handler(cls, pattern, handler):
    if isinstance(pattern, basestring):
      pattern = re.compile(pattern, re.UNICODE)
    cls.handlers.append((pattern, handler))

  @classmethod
  def register_path(cls, path, local_path, allow_listdir=False):

    def path_handler(self, parsed_url, query_dict):
      requested_path = os.path.normpath(urllib.unquote(parsed_url[2]))
      if (requested_path != path.rstrip('/') and
          not (path.endswith('/') and requested_path.startswith(path))):
        return 403, 'Forbidden: %s' % requested_path
      if path.endswith('/'):
        abs_local_path = os.path.join(local_path, requested_path[len(path):])
      else:
        abs_local_path = local_path
      abs_local_path = os.path.abspath(abs_local_path)
      if not os.path.exists(abs_local_path):
        return 404, 'Path not found: %s' % requested_path
      if not os.access(abs_local_path, os.R_OK):
        return 401, 'Access denied: %s' % requested_path
      if os.path.isdir(abs_local_path) and not allow_listdir:
        return 403, 'Forbidden: %s' % requested_path
      return self._path_handler(abs_local_path, requested_path)

    if path.endswith('/'):
      pattern = '%s(?:/.*)?$' % re.escape(path[:-1])
    else:
      pattern = '%s$' % re.escape(path)
      allow_listdir = False
    cls.register_handler(pattern, path_handler)

  def log_message(self, format, *args):
    logger.info('Request from %s: %s' %
                (self.client_address[0], format % args),
                caller_level=4)

  def do_GET(self):
    parsed_url = scheme, netloc, path, params, query, frag = urlparse.urlparse(
        self.path)
    if self.client_address[0] == self.request.getsockname()[0]:
      self.localhost = True
    else:
      self.localhost = False

    for pattern, handler in self.handlers:
      if pattern.match(path):
        reply = handler(self, parsed_url, cgi.parse_qs(query, True))
        break
    else:
      reply = 404, 'Page not found: %s' % self.path

    try:
      self.__send_reply(*reply)
    except socket.error:
      self.log_message('Caught %s %s', *sys.exc_info()[:2])

  def __send_reply(self, code, message, headers=None):
    if code != 200:
      self.send_error(code, message)
      return

    self.send_response(code)
    if not isinstance(message, basestring):
      message.seek(0)
      message = message.read()

    if headers is None:
      headers = {}
    headers.setdefault('Cache-Control', 'no-cache')
    headers.setdefault('Content-Type', 'text/html; charset=UTF-8')
    headers.setdefault('Content-Length', len(message))
    for k, v in headers.iteritems():
      self.send_header(k, v)
    self.end_headers()
    self.wfile.write(message)

  def _path_handler(self, path, requested_path):

    def gmt_time(t):
      return time.strftime('%a, %d %b %Y %H:%M:%S GMT', time.gmtime(t))

    def guess_type(path):
      if not mimetypes.inited:
        mimetypes.init()
        mimetypes.add_type('text/javascript', '.js')
      return mimetypes.guess_type(path)[0] or 'text/plain'

    def listdir(path):
      dirs = []
      files = []
      for f in os.listdir(path):
        if os.path.isdir(os.path.join(path, f)):
          dirs.append('%s/' % f)
        else:
          files.append(f)
      dirs.sort()
      dirs.insert(0, '..')
      files.sort()
      ul = tag.ul
      for f in itertools.chain(dirs, files):
        ul += tag.li(tag.a(cgi.escape(f),
                           href=urllib.quote(os.path.join(requested_path, f))),
                     _s=True)
      title = cgi.escape(requested_path)
      return str(tag.html(tag.head(tag.title(title, _s=True)),
                          tag.body(tag.h2('Directory listing for %s' % title,
                                          _s=True),
                                   tag.hr(_nc=True),
                                   ul,
                                   tag.hr(_nc=True))))

    path = os.path.abspath(path)
    cached_mtime_and_size, cached_content, cached_content_type = (
        self.path_cache.get(path, (None, None, None)))
    try:
      mtime_and_size = utils.mtime_and_size(path)
      if mtime_and_size != cached_mtime_and_size:
        if os.path.isdir(path):
          content = listdir(path)
          content_type = 'text/html; charset=UTF-8'
        else:
          content = file(path).read()
          content_type = guess_type(path)
      else:
        content = cached_content
        content_type = cached_content_type
    except IOError:
      return 500, 'Caught %s %s' % tuple(sys.exc_info()[:2])

    if cached_mtime_and_size != mtime_and_size:
      if len(content) <= 64 * 1024:
        self.path_cache[path] = mtime_and_size, content, content_type
      elif cached_mtime_and_size:
        del self.path_cache[path]

    now = time.time()
    SECONDS_IN_A_YEAR = 365 * 24 * 60 * 60
    headers = {'Content-Type': content_type,
               'Expires': gmt_time(now + SECONDS_IN_A_YEAR),
               'Last-Modified': gmt_time(mtime_and_size[0]),
               'Cache-Control': 'public, max-age=%s' % SECONDS_IN_A_YEAR}
    return 200, content, headers


def start(port):

  def serve_forever():
    while True:
      try:
        server.started_time = time.ctime()
        server.serve_forever()
      except:
        logger.error('Caught exception from server.serve_forever()\n%s' %
                     traceback.format_exc())

  if port:
    server = BaseHTTPServer.HTTPServer(('', port), RequestHandler)
    new_thread = threading.Thread(target=serve_forever,
                                  name='Thread-HTTPServer')
    new_thread.setDaemon(True)
    new_thread.start()
    logger.error('HTTP server on http://%s:%d' %
                 (server.server_name, server.server_port))
    return server

  return None
